ice.js 3 的体验优化策略
Chrome 在去年成立了一个名为 Aurora[1] 的项目,深入主流的框架和工具,比如 next.js、nuxt、 angular、webpack 等,探索如何结合框架和工具,帮助 Web 开发者用更低的成本,构建出更好的体验。
ice.js 虽不在这个项目之中,却也是在设计之初就秉持着这种理念,来实现整个框架的。无论是路由方案、数据加载方案、渲染方案,都是围绕着体验而设计。这是因为,我们都越来越意识到,框架掌控着整个应用的生命周期,是最适合从流程上来做出优化、提升体验的。好的体验,应该成为业务开发时的一个顺其自然的产物。
反观目前,我们的 Web 体验则还是一种 开发者重参与
的模式,这种方式往往是:
先开发后治理,在完成业务开发,甚至上线后,再进行性能的评估,分析诊断优化点,实施相应的优化策略 对开发者要求高,需要每个开发者都能熟练分析各种性能问题,掌握各类场景的体验优化方案 不可持续,容易随着业务迭代又发生退化
如何改变现状,借助于框架设计,普遍提升业务的体验基线,而不必要求人人成为 Web 体验的专家,这正是 ice.js 3 在探索解决的问题。
本文会简单介绍 ice.js 3 中已经落地和正在进行的一些体验优化策略,以供探讨。
ice.js GitHub 仓库:https://github.com/alibaba/ice
ice.js 3 地址:https://v3.ice.work/
加载策略
既是 MPA 也是 SPA
ice.js 3 没有区分 MPA 和 SPA,而是对二者做了融合优化。这是因为传统的 MPA 和 SPA 都存在着一定的局限性:
MPA 应用,为每一张页面都独立构建 HTML 和 JS Bundle,页面间资源不共享,页面跳转时,共用的资源被重复加载和执行。 SPA 应用,所有页面复用一张 HTML 和 入口 JS,依赖于主 Bundle 进行路由匹配后,才加载对应的页面,整个过程是串行。
在 ice.js 3 中,我们对此做出了改进:
每张页面都会构建产生自己的 HTML ,并在 HTML 中引入了当前页面所需的资源,从而避免资源的串行加载。(构建产物类 MPA) 页面间跳转时,只会加载下一跳页面特有的 Bundle,避免了资源的重复加载。(资源加载逻辑类 SPA)
框架希望通过这种方式,来将默认的资源加载逻辑调到最优。
区块的并行化加载
在 ice.js 3 中,一张页面,可以由多个 布局组件
和 一个 页面入口组件
嵌套而成,也称为 嵌套路由
。针对这类场景,ice.js 应用了以下优化,来让页面达成更好的性能体验:
布局组件和页面入口的 资源
和数据请求
会被并行加载,以达到最快的资源加载速度。路由间跳转时,比如从 /sales/recommends
跳转到/sales/favorites
,框架只会加载差异化的组件favorites.tsx
进行渲染,而不会重新渲染他们共用的布局组件
。
利用框架对 嵌套路由
所做的优化,我们可以将页面中逻辑相对分离的部分,用 嵌套路由
的方式来组织,以获得更好的加载体验。
例如,下面这种常见的移动端营销页,可以对页面内容进行拆分:
将顶部通用的 Slider
抽象为布局组件
将不同 tab 下对应的瀑布流,抽象为 路由组件
。
这样,Slider
和 瀑布流
就可以做到并行加载,并且当切换 tab 时,新的 tab 内容将由框架触发按需加载和渲染。
数据的并行化加载
在常规的 React 或 Rax 应用 中,数据请求一般都会在组件首次 useEffect
时发起。这种组织方式,数据依赖于业务 Bundle 的加载、解析、执行,会在页面完成首次渲染后才发起,请求的时机是非常滞后的。
在 ice.js 3 中,框架对页面数据加载的编码规范做出了约定,来最大限度的提前页面的数据加载时机。页面数据请求通过 dataLoader
声明后,会由框架(或容器)统一发起,和业务 Bundle 的加载解析是并行、不阻塞的。在手淘等 PHA 容器下,这种标准化的数据请求,还会被进一步提升为数据预请求,而无需额外的配置。
基于这种模式开发的 Web 应用,天然获得了更好的性能体验。
两种编码方式的请求时机对比如下:
云端结合的预请求
建设中
数据的预请求,已经是体验优化的一种主流手段,它将页面的数据请求和资源请求并行化起来。
但其实结合云和端,页面的 资源加载
和 数据加载
过程,可以被更彻底的并行化为下面的形式:
实现更彻底的预请求的前提是:当容器获得一个页面的 URL 地址后,就能知道这个 URL 对应的 Assets、数据接口信息,然后直接发起这些资源的请求。目前,ice.js 正结合云(服务端)和 端(手淘容器)在尝试打通这条链路,以帮助业务获得更好的性能体验。
渲染策略
默认 HTML 不再空白
常规的 CSR 应用,构建的 HTML 一般只包含一个容器节点,页面的初始状态是空白的,具体内容依赖 JS 的渲染。
然而,我们的页面内容,并不总是全部依赖于动态数据的,页面的基本框架结构常常都是静态的,这部分内容是完全可以在构建时就生成的。这样,页面的白屏时间将被大大缩短,用户体感会从【白屏->页面加载完成】转变为【静态页面->动态数据渲染完成】。
出于这个原因,ice.js 3 默认开启了 SSG
,会在构建时,执行页面渲染逻辑,预先构建好页面中不依赖于数据的静态部分。
开箱即用的 SSR
SSR 作为体验优化的杀手锏,已在商品详情、用户增长、淘菜菜等业务中普遍使用和验证,可以预估在未来还会有着更广泛的应用。因此,ice.js 3 是将 SSR 作为主要的渲染模式来保障的。
ice.js 3 的 SSR 设计,延续了 Rax 中结合 Midway FaaS 的一体化模式,业务可以在一个工程中完成页面逻辑和渲染服务的维护,以 Serverless 的形式发布 SSR 应用。同时框架还内置了环境变量模拟、错误降级等常用逻辑,来尽可能降低 SSR 应用的开发成本。
页面内容的流式渲染
建设中
传统的 SSR 渲染是一个同步的过程,需要等到所有的数据请求都完成后,再一次性进行整张页面的渲染,然后把渲染完成的 HTML 字符串下发给浏览器。这种模式下,如果应用中某个依赖的数据接口耗时比较久,就有可能拖慢整张页面的渲染时间,反映到用户体感上就是页面的白屏时间过长。
因此,对页面的内容分批、流式的返回,会是一种用户体感更好的方案。比如,页面的某个区块响应耗时比较久,就可以先渲染 Loading 状态,等待 Server 端完成这个区块的渲染后,再下发这部分的内容。
目前,ice.js 和上下游团队,正在基于 React 18 提供的流式渲染能力,在业务中探索面向业务的解决方案。
Zero-Bundle-Size 的探索
准备开工
Zero Bundle Size
是 React 在推 Server Components[2]时提出的一个概念,意思是在 Server 端完成渲染的组件,就不需要再向 Client 端下发 JS Bundle 了。目前社区流行的 Islands Architecture[3]也可以看做是类似的方案。
这是相对于现有的 SSR 应用而言的,我们知道,SSR 应用即使在 Server 端完成了整张页面的渲染,在 Client 端依然需要下发完整的 JS Bundle,重新执行一遍,来完成 Hydrate
,从而让页面达到可交互状态。
而在 Server Components 方案中,页面被划分为两类组件:
Server Component,只在 Server 端执行的组件,用于渲染静态内容 Client Component,只在 Client 端执行的组件,用于处理有交互的内容
浏览器侧,只需要加载 Client Component
对应的 JS Bundle 即可,可以预期的是页面的资源大小将会大大减小。以 React 官方的 Server Component Demo[4] 为例,Client 端加载的 Bundle 大小从 498k
减少到了 178k
。Client 端资源减少,带来的直接收益是,页面的可交互时间将被大大提升。
这将会是 ice.js 在下一阶段的一个主要探索方向,期待通过这种模式,能进一步的提升业务的用户体验。
小结
以上就是 ice.js 3 已经和正在建设的一些体验优化能力,可以简单小结为两个方向:
在加载策略上,优化了默认的页面资源加载策略,提供了区块和数据的并行化加载能力,同时还在探索如果结合云和端,建设更彻底的预请求方案。 在渲染策略上,提供了 SSG 能力以优化默认的 HTML 构建产物,在现有 SSR 能力的基础上,还将继续探索流式渲染、Server Components 等方案,进一步提升 SSR 应用的体验。
目前 ice.js 3[5] 已经正式发布,期待更多的业务来使用、验证,一起建设更好的用户体验。
参考资料
Aurora: https://developer.chrome.com/blog/introducing-aurora/
[2]Server Components: https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html
[3]Islands Architecture: https://www.patterns.dev/posts/islands-architecture/
[4]Server Component Demo: https://github.com/reactjs/server-components-demo
[5]ice.js 3: https://v3.ice.work/